//
//  MKAPIHttpRequestAFNetworking.m
//  MKAPI
//
//  Created by 苏尚进 on 2022/8/26.
//  Copyright © 2022 moon. All rights reserved.
//

#import "MKAPIHttpRequestAFNetworking.h"
#import "MKAPIPreCompile.h"
#import "MKAPIRequestParam+AFNetworking.h"
#import "NSObject+MKAPI.h"
#import "NSError+MKAPIHttpRequest.h"
@interface MKAPIHttpRequestAFNetworking(){
}
@property(nonatomic,strong,nullable) NSURLSessionTask *sessionTask;

@property(nonatomic,strong) NSProgress *uploadProgress;//上传进度
@property(nonatomic,strong) NSProgress *downloadProgress;//下载进度
@property(nonatomic,strong) AFHTTPSessionManager *httpSessionManager;
@property(nonatomic,strong,nullable) NSURLAuthenticationChallenge *authenticationChallenge;//ssl证书检验过程的证书链信息,通过challenge.protectionSpace.serverTrust，可以获取到证书链
@property(nonatomic,strong,nullable) NSError *authChallengeError;
@end

@implementation MKAPIHttpRequestAFNetworking
- (id)init{
    if (self=[super init]) {
        self.validateServerHttpsCerts = NO;
    }
    return self;
}
- (void)__configRequest:(NSMutableURLRequest *)request{
    MKAPIRequestParam *param = self.requestParam;
    request.cachePolicy = param.cachePolicy;
    request.timeoutInterval = param.timeoutInterval;
    request.networkServiceType = param.networkServiceType;
    request.allowsCellularAccess = param.allowsCellularAccess;
    if (@available(iOS 13.0, *)) {
        request.allowsExpensiveNetworkAccess = param.allowsExpensiveNetworkAccess;
    }
    if (@available(iOS 13.0, *)) {
        request.allowsConstrainedNetworkAccess = param.allowsConstrainedNetworkAccess;
    }
    if (@available(iOS 14.5, *)) {
        request.assumesHTTP3Capable = param.assumesHTTP3Capable;
    }
    if (@available(iOS 15.0, *)) {
        request.attribution = param.attribution;
    }
    [self willSendRequest:request];
}
- (BOOL)shouldValidateServerHttpsCerts{
    NSURL *baseUrl = [NSURL URLWithString:self.requestParam.url];
    BOOL should = (self.validateServerHttpsCerts||self.validateClientHttpsCerts) && [baseUrl.scheme isEqualToString:@"https"];
    return should;
}
//setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential) {
- (NSURLSessionAuthChallengeDisposition)sessionAuthenticationChallengeWithSession:(NSURLSession *)session challenge:(NSURLAuthenticationChallenge *)challenge credential:(NSURLCredential *_Nullable*_Nullable)credential{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSString *authenticationMethod = challenge.protectionSpace.authenticationMethod;
    if([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
        if (self.validateServerHttpsCerts) {
            //验证服务器ssl证书
            NSArray<NSData *> *httpsCerts = self.serverHttpsCerts;
            if([challenge mkapi_verifyServerTrustWithCerts:httpsCerts]){
                disposition = NSURLSessionAuthChallengeUseCredential;
                *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            }else{
                self.authChallengeError = [self __serverTrustErrorForServerTrust:self.authenticationChallenge.protectionSpace.serverTrust url:self.sessionTask.originalRequest.URL];
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //不开启验证默认放过
            disposition = NSURLSessionAuthChallengeUseCredential;
            *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        }
    }else if(self.validateClientHttpsCerts && [authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]){
//                //验证客户端ssl证书
        *credential = self.clientHttpsCert;
        disposition = self.clientHttpsCert==nil ? NSURLSessionAuthChallengeCancelAuthenticationChallenge : NSURLSessionAuthChallengeUseCredential;
    }
    return disposition;
}
- (void)willSendRequest:(NSMutableURLRequest *)request{
    
}
- (void)doRunTask{
    [super doRunTask];
    @MKAPI_WEAKIFY(self);
    self.authChallengeError = nil;
    NSURL *baseUrl = [NSURL URLWithString:self.requestParam.url];
    self.httpSessionManager = [[MKAPIAFHTTPSessionManager alloc] initWithBaseURL:baseUrl sessionConfiguration:self.sessionConfiguration];
    
    self.httpSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
    if([self shouldValidateServerHttpsCerts]){
        NSArray<NSData *> *httpsCerts = self.serverHttpsCerts;
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[NSSet setWithArray:httpsCerts]];
        securityPolicy.validatesDomainName = YES;
        securityPolicy.allowInvalidCertificates = NO;
        self.httpSessionManager.securityPolicy = securityPolicy;
        
        //如何在iOS 12上绕过SSL Pinning (ssl-kill-switch2越狱插件原理) https://www.anquanke.com/post/id/179514
        //SessionDidReceiveAuthenticationChallengeBlock优先于AuthenticationChallengeHandler
        //移动应用安全基础篇：APP抓包姿势总结 https://www.freebuf.com/articles/web/207041.html
        
    //    [self setAuthenticationChallengeHandler:^id _Nonnull(NSURLSession * _Nonnull session, NSURLSessionTask * _Nonnull task, NSURLAuthenticationChallenge * _Nonnull challenge, void (^ _Nonnull completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable)) {
    //        NSLog(@"setAuthenticationChallengeHandler");
    //        NSLog(@"auth task:%@,%@",@(task.taskIdentifier),task.originalRequest);
    //        task.mkapi_authenticationChallenge = challenge;
    //        NSLog(@"authenticationMethod:%@",challenge.protectionSpace.authenticationMethod);
    //        return @(NSURLSessionAuthChallengePerformDefaultHandling);
    //    }];
        //通过hook - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain方法，可以进行绕过ssl检验，因此不使用该方法进行ssl验证
        
        //由于session级别的ssl验证，针对同一个session，同类型的请求（如下载、上传、获取Data）只会验证一次，因此针对每个请求，都要实例一个session对象。做到每个请求都验证ssl证书。
        @MKAPI_WEAKIFY(self);
        [self.httpSessionManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential) {
            @MKAPI_NORMALIZE(self);
//            NSLog(@"setSessionDidReceiveAuthenticationChallengeBlock");
            self.authenticationChallenge = challenge;
            NSURLSessionAuthChallengeDisposition disposition = [self sessionAuthenticationChallengeWithSession:session challenge:challenge credential:credential];
            return disposition;
        }];
    }else{
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
        securityPolicy.allowInvalidCertificates = YES;
        securityPolicy.validatesDomainName = NO;
        self.httpSessionManager.securityPolicy = securityPolicy;
    }
    
    //https测试网站：https://httpbin.org 或 https://httpbingo.org
    NSUInteger baseTotalUnitCount = 10000;
    switch (self.requestType) {
        case MKAPIHttpRequestTypeData:{
            NSError *error;
            NSMutableURLRequest *request = [self.requestParam af_requestWithError:&error];
            _request = request;
            if(!request){
                [self completeWithError:error];
                return;
            }
            [self __configRequest:request];
            self.sessionTask = [self.httpSessionManager dataTaskWithRequest:request uploadProgress:^(NSProgress * _Nonnull uploadProgress) {
                @MKAPI_NORMALIZE(self);
                [self __requestDidChangeUploadProgress:uploadProgress];
            } downloadProgress:^(NSProgress * _Nonnull downloadProgress) {
                @MKAPI_NORMALIZE(self);
                [self __requestDidChangeDownloadProgress:downloadProgress];
            } completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                @MKAPI_NORMALIZE(self);
                [self __requestDidComplete:response responseObject:responseObject error:error];
            }];
            self.progress.totalUnitCount = baseTotalUnitCount*2;
            self.uploadProgress = [NSProgress progressWithTotalUnitCount:baseTotalUnitCount];
            self.downloadProgress = [NSProgress progressWithTotalUnitCount:baseTotalUnitCount];
            [self.progress addChild:self.uploadProgress withPendingUnitCount:baseTotalUnitCount];
            [self.progress addChild:self.downloadProgress withPendingUnitCount:baseTotalUnitCount];
            [self.sessionTask resume];
        }
            break;
        case MKAPIHttpRequestTypeDownload:{
            NSError *error;
            NSMutableURLRequest *request = [self.requestParam af_requestWithError:&error];
            _request = request;
            if(!request){
                [self completeWithError:error];
                return;
            }
            [self __configRequest:request];
            self.sessionTask = [self.httpSessionManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
                @MKAPI_NORMALIZE(self);
                [self __requestDidChangeDownloadProgress:downloadProgress];
            } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
                if(self.responseFile) return self.responseFile;
                return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@.res",targetPath.path]];//原始的targetPath为临时文件，在下载完成后会被移除掉，需要将该文件移动到新的路径上。
            } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
                @MKAPI_NORMALIZE(self);
                if(!self)return;
                self->_responseFile = filePath;
                [self __requestDidComplete:response responseObject:nil error:error];
            }];
            self.progress.totalUnitCount = baseTotalUnitCount;
            self.downloadProgress = [NSProgress progressWithTotalUnitCount:baseTotalUnitCount];
            [self.progress addChild:self.downloadProgress withPendingUnitCount:baseTotalUnitCount];
            [self.sessionTask resume];
        }
            break;
        case MKAPIHttpRequestTypeUpload:{
            NSError *error;
            NSMutableURLRequest *request = [self.requestParam af_requestWithError:&error];
            _request = request;
            if(!request){
                [self completeWithError:error];
                return;
            }
            [self __configRequest:request];
            self.sessionTask = [self.httpSessionManager uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {
                @MKAPI_NORMALIZE(self);
                [self __requestDidChangeUploadProgress:uploadProgress];
            } completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                @MKAPI_NORMALIZE(self);
                [self __requestDidComplete:response responseObject:responseObject error:error];
            }];
            self.progress.totalUnitCount = baseTotalUnitCount;
            self.uploadProgress = [NSProgress progressWithTotalUnitCount:baseTotalUnitCount];
            [self.progress addChild:self.uploadProgress withPendingUnitCount:baseTotalUnitCount];
            [self.sessionTask resume];
        }
            break;
        default:
            break;
    }
}
- (void)doCancel{
    [super doCancel];
    if(self.sessionTask){
        [self.sessionTask cancel];
    }else{
        [self completeWithError:[NSError mkapi_errorWithCancelTask:self]];
    }
}
- (void)__requestDidChangeUploadProgress:(NSProgress *)uploadProgress{
//    NSLog(@"UploadProgress:%@",uploadProgress.mkapi_description);
    [self updateProgress:^(NSProgress * _Nonnull progress) {
        [self.uploadProgress mkapi_setCompletedUnitCount:uploadProgress.completedUnitCount totalUnitCount:uploadProgress.totalUnitCount];
    }];
}
- (void)__requestDidChangeDownloadProgress:(NSProgress *)downloadProgress{
//    NSLog(@"DownloadProgress:%@",downloadProgress.mkapi_description);
    [self updateProgress:^(NSProgress * _Nonnull progress) {
        [self.downloadProgress mkapi_setCompletedUnitCount:downloadProgress.completedUnitCount totalUnitCount:downloadProgress.totalUnitCount];
    }];
}
- (void)__requestDidComplete:(NSURLResponse *)response responseObject:(id)responseObject error:(NSError *)error{
//    NSLog(@"task complete:%@,%@",@(self.sessionTask.taskIdentifier),self.sessionTask.originalRequest);
    //ssl检验失败时：Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “httpbingo.org” which could put your confidential information at risk." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x6000006206e0>, NSErrorFailingURLKey=https://httpbingo.org/post, NSErrorFailingURLStringKey=https://httpbingo.org/post, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “httpbingo.org” which could put your confidential information at risk.}
    if(self.authChallengeError){
        error = self.authChallengeError;
    }else if(!error && [self shouldValidateServerHttpsCerts] && !self.authenticationChallenge){//检测到使用ssl证书绕过插件
        /**
         NSURLRequestUseProtocolCachePolicy:每个url，第一次加载时，会经过ssl验证，第二次就直接返回缓存数据，跳过ssl验证
         NSURLRequestReloadIgnoringLocalCacheData:每个url，忽略本地缓存数据，每次都会下载，经过ssl验证
         NSURLRequestReloadIgnoringLocalAndRemoteCacheData:每个url，忽略本地缓存数据，每次都会下载，经过ssl验证
         NSURLRequestReturnCacheDataElseLoad:如果url缓存还未过期，则使用缓存，跳过ssl验证
         NSURLRequestReturnCacheDataDontLoad:如果url缓存还未过期，则使用缓存，跳过ssl验证
         NSURLRequestReloadRevalidatingCacheData：每次都会刷新缓存,经过ssl验证
         */
        if(self.requestParam.httpMethod==MKAPIHttpRequestMethodPOST || [@[
            @(NSURLRequestReloadIgnoringLocalCacheData),
            @(NSURLRequestReloadIgnoringLocalAndRemoteCacheData),
            @(NSURLRequestReloadRevalidatingCacheData),
        ] containsObject:@(self.requestParam.cachePolicy)]){
            error = [self __serverTrustErrorForServerTrust:self.authenticationChallenge.protectionSpace.serverTrust url:self.sessionTask.originalRequest.URL];
        }
    }else if(error){
        error = [NSError mkapi_errorWithUrl:self.sessionTask.originalRequest.URL.absoluteString code:MKAPIRequestErrorNetworkFrameworkError serviceCode:[@(error.code) stringValue] serviceDesc:error.mkapi_displayDesc userInfo:MKAPIDictionary.PS(error.userInfo).P(NSUnderlyingErrorKey,error).dictionary];
    }
    _responseData = responseObject;
    _response = (NSHTTPURLResponse *)response;
    [self completeWithError:error];
}
- (nonnull NSError *)__serverTrustErrorForServerTrust:(nullable SecTrustRef)serverTrust url:(nullable NSURL *)url{
    return [NSError mkapi_certValidErrorForServerTrust:serverTrust url:url];
}
@end



@interface MKAPIAFHTTPSessionManager()
typedef NSURLSessionAuthChallengeDisposition (^MKAPIAFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
@property (readwrite, nonatomic, copy) MKAPIAFURLSessionDidReceiveAuthenticationChallengeBlock mkapi_sessionDidReceiveAuthenticationChallenge;
@end

@implementation MKAPIAFHTTPSessionManager

- (BOOL)respondsToSelector:(SEL)selector {
    if (selector == @selector(URLSession:didReceiveChallenge:completionHandler:)) {
        return YES;
    } else{
        return [super respondsToSelector:selector];
    }
}
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block {
    self.mkapi_sessionDidReceiveAuthenticationChallenge = block;
}
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.mkapi_sessionDidReceiveAuthenticationChallenge) {
        disposition = self.mkapi_sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
@end
